<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html ng-app="main">
  <head>
    <meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
    <script src="/chromium-common/web/angular-1.2.0/angular.min.js"></script>
    <script src="/chromium-common/web/jquery-2.1.1/jquery-2.1.1.min.js"></script>
    <script src="/chromium-common/web/ng-grid/ng-grid.min.js"></script>

    <link rel="stylesheet" href="/chromium-common/web/ng-grid/ng-grid.min.css" />
    <link rel="stylesheet" href="/chromium-common/web/bootstrap-3.2.0/bootstrap.min.css" />

    <style>
      body, html {
        height: 95%;
        width: 95%;
        margin: 0;
        margin-left: auto;
        margin-right: auto;
      }

      .wrapper {
        display: table;
        width: 100%;
        height: 100%;
      }

      .loader-spinner {
        width: 30px;
        height: 30px;
        margin-left: auto;
        margin-right: auto;
      }

      .header {
        height: 70px;
        display: table-row;
        background: OldLace;
      }

      .gridStyle {
        display: table-row;
        height: 100% !important;
      }
    </style>

    <script language="javascript">
      // Split a string in 2 parts. The first is the leading number, if any,
      // the second is the string following the numbers.
      function splitNum(s) {
        var results = new Array();
        results[0] = 'None';
        for (var i = 0; i < s.length; i++) {
          var substr = s.substr(0, i+1)
          if (isNaN(substr)) {
            // Not a number anymore.
            results[1] = s.substr(i)
            break;
          } else {
            // This is a number. update the results.
            results[0] = parseFloat(substr);
          }
        }
        return results;
      }

      // Compare 2 strings using a custom alphanumerical algorithm.
      // This is similar to a normal string sort, except that we sort
      // first by leading digits, if any.
      // For example:
      //  100hello > 2goodbye
      // Numbers anywhere else in the string are compared using the normal
      // sort algorithm.
      function alphanumCompare(a, b) {
        var parsedA = splitNum(a);
        var parsedB = splitNum(b);
        var numA = parsedA[0];
        var numB = parsedB[0];
        var strA = parsedA[1];
        var strB = parsedB[1];

        if (! (isNaN(numA) || isNaN(numB))) {
          // They both start with numbers.
          cmp = (numA - numB);
          if (cmp !== 0) {
            return cmp;
          }

          // Identical. Fallback to string.
          return (strA < strB) ? -1 : (strA > strB ? 1 : 0)
        }

        // If only one starts with a number, we start with that one as
        // the lowest.
        if (isNaN(numA) == false) return -1
        if (isNaN(numB) == false) return 1

        // They are both strings.
        return (a < b) ? -1 : (a > b ? 1 : 0)
      }

      var TYPE_ORDER = ["parent", "prefix", "storageObject"];

      function entryCompare(a, b) {
        var aType = TYPE_ORDER.indexOf(a.type);
        var bType = TYPE_ORDER.indexOf(b.type);
        if (aType != bType) {
          return aType - bType;
        }

        return alphanumCompare(a.name, b.name);
      }

      function sizeCompare(a, b) {
        return ((a || 0) - (b || 0));
      }

      // Helper function to retrieve the value of a GET query parameter.
      // Greatly inspired from http://alturl.com/8rj7a
      function getParameter(parameterName) {
        // Add '=' to the parameter name (i.e. parameterName=value)
        var parameterName = parameterName + '=';
        var queryString = window.location.search.substring(1);
        if (queryString.length <= 0) {
          return '';
        }

        // Find the beginning of the string
        begin = queryString.indexOf(parameterName);

        // If the parameter name is not found, skip it, otherwise return the
        // value.
        if (begin == -1) {
          return '';
        }

        // Add the length (integer) to the beginning.
        begin += parameterName.length;

        // Multiple parameters are separated by the '&' sign.
        end = queryString.indexOf ('&', begin);

        if (end == -1) {
          end = queryString.length;
        }

        // Return the string.
        return escape(unescape(queryString.substring(begin, end)));
      }

      String.prototype.endsWith = function(suffix) {
            return this.indexOf(suffix, this.length - suffix.length) !== -1;
      };

      var CR_COMMIT_POSITION_NUMBER_KEY = 'Cr-Commit-Position-Number';
      var CR_GIT_COMMIT_KEY = 'Cr-Git-Commit';

      function getDisplayName($scope, objectName) {
        if (objectName.indexOf($scope.prefix) == 0) {
          objectName = objectName.substring($scope.prefix.length);
        }
        if (objectName.endsWith("/")) {
          objectName = objectName.substring(0, (objectName.length - 1));
        }
        return objectName;
      }

      function getMetadata(storageObject, key) {
        // 'gsutil' uploads keys as lowercase
        return (storageObject.metadata || {})[key.toLowerCase()]
      }

      function getCommitPositionNumber(storageObject) {
        return getMetadata(storageObject, CR_COMMIT_POSITION_NUMBER_KEY);
      }

      function getGitCommit(storageObject) {
        return getMetadata(storageObject, CR_GIT_COMMIT_KEY);
      }

      function getCommitPositionNumberURL(commitPositionNumber) {
        return 'https://crrev.com/' + escape(commitPositionNumber);
      }

      function getGitCommitURL(gitCommit) {
        return 'https://crrev.com/' + escape(gitCommit);
      }

      function formatSizeUnits(bytes){
              if      (bytes>=1000000000) {bytes=(bytes/1000000000).toFixed(2)+' GB';}
              else if (bytes>=1000000)    {bytes=(bytes/1000000).toFixed(2)+' MB';}
              else if (bytes>=1000)       {bytes=(bytes/1000).toFixed(2)+' KB';}
              else if (bytes>1)           {bytes=bytes+' bytes';}
              else if (bytes==1)          {bytes=bytes+' byte';}
              else                        {bytes='0 byte';}
              return bytes;
      }

      function createEntry(type, name, icon) {
        var entry = {
            type: type,
            name: name,
            icon: icon,
        };
        entry.this = entry;
        return entry;
      }

      function getParentRowEntry($scope) {
        var backpath = location.pathname;

        // If there is more than one section delimited by '/' in the current
        // prefix we truncate the last section and append the rest to
        // backpath.
        var delimiter = $scope.prefix.lastIndexOf('/');
        if (delimiter >= 0) {
          delimiter = $scope.prefix.substr(0, delimiter).lastIndexOf('/');
          if (delimiter >= 0) {
            backpath += '?prefix=';
            backpath += escape($scope.prefix.substr(0, delimiter+1));
          }
        }

        var entry = createEntry("parent", "Parent Directory", "back.gif");
        entry.url = backpath;
        return entry;
      }

      function getPrefixEntries($scope, prefixes) {
        var entries = Array();
        prefixes.forEach(function(prefix) {
          var link = location.pathname.substr(0,
              location.pathname.indexOf('?')) + '?prefix=' + prefix;

          var entry = createEntry(
            "prefix",
            getDisplayName($scope, prefix),
            "folder.gif"
          );
          entry.prefix = prefix;
          entry.url = link;
          entries.push(entry);
        });
        return entries;
      }

      function getStorageObjectEntries($scope, items) {
        // Set up the variables.
        var entries = Array();
        items.forEach(function(storageObject) {
          // If this object _is_ the current prefix, skip it.
          if(
              (storageObject.kind !== 'storage#object') ||
              (storageObject.name === $scope.prefix)) {
            return;
          }

          var link = storageObject.mediaLink;
          var filename = getDisplayName($scope, storageObject.name);
          var lastModified = storageObject.updated.replace('T', ' ');
          lastModified = lastModified.substr(0, lastModified.indexOf('.'));

          if (filename == '') {
            return;
          }

          var entry = createEntry(
            "storageObject",
            filename,
            "binary.gif"
          );
          entry.so = storageObject;
          entry.url = link;
          entry.lastModified = lastModified;
          entry.size = storageObject.size;
          entry.commit = getGitCommit(storageObject);
          entry.commitPositionNumber = getCommitPositionNumber(storageObject);
          entries.push(entry);
        });
        return entries;
      }

      function fetchAndDisplayObjects($scope, $http) {
        var gsAPIBase = 'https://www.googleapis.com/storage/v1';
        var fieldsParam = 'items(kind,mediaLink,metadata,name,size,updated),' +
                          'kind,prefixes,nextPageToken';
        var gsAPIListObjects = gsAPIBase + '/b/' + $scope.bucket + '/o';
        var url = gsAPIListObjects + '?delimiter=/&prefix=' + $scope.prefix +
                  '&fields=' + fieldsParam;

        // Initial page load
        $scope.loading = true;
        addNextPage('')

        function addNextPage(nextPageToken) {
          var reqURL = url
          if (nextPageToken != '') {
            reqURL += '&pageToken=' + nextPageToken;
          }

          var req = $http.get(reqURL).
            success(function(data, status, headers, config) {
              if(data.kind != 'storage#objects') {
                console.log("JSON API did not return 'storage#objects' object");
                return;
              }

              var entries = Array();

              // Augment prefixes
              if (data.prefixes !== undefined) {
                entries.push.apply(
                    entries,
                    getPrefixEntries($scope, data.prefixes)
                );
              }

              // Augment items
              if (data.items !== undefined) {
                entries.push.apply(
                    entries,
                    getStorageObjectEntries($scope, data.items)
                );
              }

              // Add all generated entries to our table data.
              $scope.data.push.apply($scope.data, entries);

              // Load next page
              if(data.nextPageToken !== undefined) {
                return addNextPage(data.nextPageToken);
              }

              // Finished loading.
              $scope.loading = false;
            }).
            error(function(data, status, headers, config) {
              console.log("HTTP request failed with [" + status + "]");
            });
          return req;
        }
      }

      var app = angular.module('main', ['ngGrid'])
      app.controller('ChromeGSIndex', function($scope, $http, $sce) {
        $scope.data = new Array();

        var lastSlash = location.pathname.lastIndexOf("/");

        $scope.root = location.pathname.substring(0, lastSlash);
        $scope.prefix = getParameter('prefix');
        $scope.iconPath = "/chromium-common/images";
        $scope.loading = false;

        var bucket = $scope.root;
        if(bucket[0] == "/") {
          bucket = bucket.substring(1);
        }
        $scope.bucket = bucket;

        // Add a parent directory entry, if applicable
        if ($scope.prefix != "") {
          $scope.data.push(getParentRowEntry($scope));
        }

        $scope.filterOptions = {
          filterText: '',
        };

        $scope.commitPositionNumberHtml = function(entry) {
          var html;
          if (entry.type != "storageObject") {
            html = "";
          } else if (entry.commitPositionNumber) {
            var link = getCommitPositionNumberURL(entry.commitPositionNumber);
            html = "<a href='" + link + "'>" +
                     entry.commitPositionNumber +
                   "</a>";
          } else {
            html = "-";
          }
          return $sce.trustAsHtml(html);
        };

        $scope.commitHtml = function(entry) {
          var html;
          if (entry.type != "storageObject") {
            html = "";
          } else if (entry.commit) {
            var link = getGitCommitURL(entry.commit);
            html = "<a href='" + link + "'>" +
                     entry.commit +
                   "</a>";
          } else {
            html = "-";
          }
          return $sce.trustAsHtml(html);
        };

        $scope.getSizeHtml = function(value) {
          var html;
          // This checks for value in 'undefined' and 'null'.
          if (value == null) {
            html = "";
          } else {
            html = formatSizeUnits(value);
          }
          return $sce.trustAsHtml(html);
        };

        $scope.gridOptions = {
          data: 'data',
          enableRowSelection: false,
          enableColumnResize: true,
          filterOptions: $scope.filterOptions,
          columnDefs: [
            {
              displayName: "",
              field: "icon",
              width: "20px",
              cellTemplate: "<img src='{{iconPath}}/{{row.entity.icon}}' />",
            },
            {
              displayName: "Name",
              field: "this",
              width: "***",
              sortFn: entryCompare,
              cellTemplate:
                "<div>" +
                  "<a href='{{row.entity.url}}'>{{row.entity.name}}</a>" +
                "</div>",
            },
            {
              displayName: "Last Modified",
              field: "lastModified",
            },
            {
              displayName: "Size",
              field: "size",
              sortFn: sizeCompare,
              cellTemplate:
                "<div ng-bind-html='getSizeHtml(row.entity.size)'></div>",
            },
            {
              displayName: "Commit Position",
              field: "commitPositionNumber",
              cellTemplate:
                "<div ng-bind-html='commitPositionNumberHtml(row.entity)'></div>",
            },
            {
              displayName: "Commit",
              field: "commit",
              width: "**",
              cellTemplate:
                "<div ng-bind-html='commitHtml(row.entity)'></div>",
            },
          ],
        };

        fetchAndDisplayObjects($scope, $http);
      });
    </script>
  </head>
  <body ng-controller="ChromeGSIndex">
    <script type="text/ng-template" id="header">
      <h1>
        Index of <em>{{bucket}}</em>/{{prefix}}</span>
        <img class="loader-spinner" ng-show="loading"
            src="/chromium-common/images/spinner.gif"></img>
      </h1>
    </script>

    <div class="wrapper">
      <div class="header" ng-include="'header'"></div>
      <strong>Filter:</strong><input type="text" ng-model="filterOptions.filterText" />
      <div class="gridStyle" ng-grid="gridOptions"></div>
    </div>
  </body>
</html>
